package site.yunnong.atvris.service.impl;

import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import site.yunnong.atvris.common.api.Page;
import site.yunnong.atvris.common.dto.SearchVideoParam;
import site.yunnong.atvris.common.dto.VideoQueryParam;
import site.yunnong.atvris.common.enumeration.MediaStatusEnum;
import site.yunnong.atvris.common.enumeration.VideoSortEnum;
import site.yunnong.atvris.common.enumeration.VideoTimeTypeEnum;
import site.yunnong.atvris.common.utils.DateUtils;
import site.yunnong.atvris.dao.domain.ext.VideoDetail;
import site.yunnong.atvris.dao.mapper.ext.VideoExtMapper;
import site.yunnong.atvris.service.EsVideoService;
import site.yunnong.atvris.service.MediaPartitionService;
import site.yunnong.atvris.service.domain.EsVideo;
import site.yunnong.atvris.service.repository.EsVideoRepository;
import site.yunnong.atvris.service.vo.VideoCardVO;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author zjh
 * @date 2021/9/26 16:46
 */
@Slf4j
@Service
public class EsVideoServiceImpl implements EsVideoService {

    @Resource
    private VideoExtMapper videoExtMapper;
    @Autowired
    private EsVideoRepository esVideoRepository;
    @Resource
    private ElasticsearchRestTemplate elasticsearchRestTemplate;
    @Resource
    private MediaPartitionService mediaPartitionService;

    // 自动加载
//    @PostConstruct
    public void init() {
        log.info("加载视频数据到es中...");
        importAll();
        log.info("es视频数据加载完成");
    }

    @Override
    public int importAll() {
        VideoQueryParam videoQueryParam = new VideoQueryParam();
        // 审核通过的
        videoQueryParam.setStatus(MediaStatusEnum.APPROVED.getValue());
        List<VideoDetail> videoDetailList = videoExtMapper.queryUserVideoList(videoQueryParam);
        // videoDetail 转 EsVideo
        List<EsVideo> esVideoList = Optional.of(videoDetailList).orElse(Lists.newArrayList())
                .stream().map(this::buildEsVideo).collect(Collectors.toList());
        Iterable<EsVideo> esVideoIterable = esVideoRepository.saveAll(esVideoList);
        int result = 0;
        Iterator<EsVideo> iterator = esVideoIterable.iterator();
        while (iterator.hasNext()) {
            iterator.next();
            result++;
        }
        return result;
    }

    private EsVideo buildEsVideo(VideoDetail videoDetail) {
        EsVideo esVideo = new EsVideo();
        BeanUtils.copyProperties(videoDetail, esVideo);
        esVideo.setId(videoDetail.getUvid());
        return esVideo;
    }

    @Override
    public void delete(Long id) {
        esVideoRepository.deleteById(id);
    }

    @Override
    public void save(Long uvid) {
        VideoQueryParam videoQueryParam = new VideoQueryParam();
        // 审核通过的
        videoQueryParam.setStatus(MediaStatusEnum.APPROVED.getValue());
        videoQueryParam.setUvid(uvid);
        List<VideoDetail> videoDetailList = videoExtMapper.queryUserVideoList(videoQueryParam);
        if (CollectionUtils.isNotEmpty(videoDetailList)) {
            EsVideo esVideo = buildEsVideo(videoDetailList.get(0));
            esVideoRepository.save(esVideo);
        }
    }

    @Override
    public void delete(List<Long> ids) {
        if (!CollectionUtils.isEmpty(ids)) {
            List<EsVideo> esVideoList = new ArrayList<>();
            for (Long id : ids) {
                EsVideo esVideo = new EsVideo();
                esVideo.setId(id);
                esVideoList.add(esVideo);
            }
            esVideoRepository.deleteAll(esVideoList);
        }
    }

    @Override
    public Page<VideoCardVO> search(SearchVideoParam param) {
        // jpa 页码从0开始
        Pageable pageable = PageRequest.of(param.getPageNum() - 1, param.getPageSize());
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 分页
        queryBuilder.withPageable(pageable);
        String keyword = param.getKeyword();
        // 过滤
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        // 分区
        Long pid = param.getPid();
        if (pid != null) {
            // 查到所有子pid
            List<Long> pidList = mediaPartitionService.queryChildPidList(pid);
            // 筛选所有pid
            boolQueryBuilder.must(QueryBuilders.termsQuery("pid", pidList));
        }
        // 时长
        Integer searchVideoTimeType = param.getSearchVideoTimeType();
        if (VideoTimeTypeEnum.ONE_MINITE.getValue().equals(searchVideoTimeType)) {
            boolQueryBuilder.must(QueryBuilders.rangeQuery("videoTime")
                    .lt(VideoTimeTypeEnum.ONE_MINITE.getRightSecondLimit()));
        } else if (VideoTimeTypeEnum.TEN_MINITE.getValue().equals(searchVideoTimeType)) {
            boolQueryBuilder.must(QueryBuilders.rangeQuery("videoTime")
                    .from(VideoTimeTypeEnum.TEN_MINITE.getLeftSecondLimit())
                    .to(VideoTimeTypeEnum.TEN_MINITE.getRightSecondLimit()));
        } else if (VideoTimeTypeEnum.HALF_HOUR.getValue().equals(searchVideoTimeType)) {
            boolQueryBuilder.must(QueryBuilders.rangeQuery("videoTime")
                    .from(VideoTimeTypeEnum.HALF_HOUR.getLeftSecondLimit())
                    .to(VideoTimeTypeEnum.HALF_HOUR.getRightSecondLimit()));
        } else if (VideoTimeTypeEnum.OVER_HALF_HOUR.getValue().equals(searchVideoTimeType)) {
            boolQueryBuilder.must(QueryBuilders.rangeQuery("videoTime")
                    .gt(VideoTimeTypeEnum.OVER_HALF_HOUR.getLeftSecondLimit()));
        }
        queryBuilder.withFilter(boolQueryBuilder);
        // 关键字
        if (StringUtils.isBlank(keyword)) {
            return new Page<>(param.getPageNum(), param.getPageSize(), 0L, Lists.newArrayList());
        } else {
            // 关键字
            List<FunctionScoreQueryBuilder.FilterFunctionBuilder> filterFunctionBuilders = Lists.newArrayList();
            filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("title", keyword),
                    ScoreFunctionBuilders.weightFactorFunction(10)));
            filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("partitionName", keyword),
                    ScoreFunctionBuilders.weightFactorFunction(5)));
            filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("userNickname", keyword),
                    ScoreFunctionBuilders.weightFactorFunction(2)));
            FunctionScoreQueryBuilder.FilterFunctionBuilder[] builders = new FunctionScoreQueryBuilder.FilterFunctionBuilder[filterFunctionBuilders.size()];
            filterFunctionBuilders.toArray(builders);
            FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(builders)
                    .scoreMode(FunctionScoreQuery.ScoreMode.SUM)
                    .setMinScore(2);
            queryBuilder.withQuery(functionScoreQueryBuilder);
        }
        // 排序
        Integer sort = param.getSearchSortType();
        if (VideoSortEnum.COMPREHENSIVE_SORT.getValue().equals(sort)) {
            // 综合排序 todo
            queryBuilder.withSort(SortBuilders.scoreSort().order(SortOrder.DESC));
        } else if (VideoSortEnum.MOST_HITS_SORT.getValue().equals(sort)) {
            // 最多播放
            queryBuilder.withSort(SortBuilders.fieldSort("viewCount").order(SortOrder.DESC));
        } else if (VideoSortEnum.LASTEST_RELEASE_SORT.getValue().equals(sort)) {
            // 最新发布
            queryBuilder.withSort(SortBuilders.fieldSort("uploadTime").order(SortOrder.DESC));
        } else if (VideoSortEnum.MOST_STARS_SORT.getValue().equals(sort)) {
            // 最多收藏
            queryBuilder.withSort(SortBuilders.fieldSort("starCount").order(SortOrder.DESC));
        }
        // todo 高亮存在问题，改为前端处理高亮
//        // 高亮字段
//        HighlightBuilder highlightBuilder = new HighlightBuilder();
//        highlightBuilder.field("title");
//        // 关闭多个高亮
//        highlightBuilder.requireFieldMatch(false);
//        highlightBuilder.preTags("<span style='color:red'>");
//        highlightBuilder.postTags("</span>");
//        queryBuilder.withHighlightBuilder(highlightBuilder);
        
        NativeSearchQuery searchQuery = queryBuilder.build();
        log.info("DSL:{}", Objects.requireNonNull(searchQuery.getQuery()));
        SearchHits<EsVideo> searchHits = elasticsearchRestTemplate.search(searchQuery, EsVideo.class);
        if (searchHits.getTotalHits() <= 0) {
            return new Page<>(param.getPageNum(), param.getPageSize(), 0L, Lists.newArrayList());
        }
        List<VideoCardVO> videoCardVOList = searchHits.stream().map(hit -> {
            EsVideo esVideo = hit.getContent();
            return buildVideoCardVO(esVideo);
        }).collect(Collectors.toList());
        return new Page<>(param.getPageNum(), param.getPageSize(), searchHits.getTotalHits(), videoCardVOList);
    }

    private VideoCardVO buildVideoCardVO(EsVideo esVideo) {
        VideoCardVO vo = new VideoCardVO();
        BeanUtils.copyProperties(esVideo, vo);
        vo.setUvid(esVideo.getId());
        vo.setDuration(DateUtils.covertSecondToDuration(esVideo.getVideoTime()));
        return vo;
    }

}
